package cn.mrcode.wxsdk.core.dialogue.common.accessToken.lifeCycle.single;


import cn.mrcode.wxsdk.core.dialogue.common.accessToken.AccessTokenApi;
import cn.mrcode.wxsdk.core.dialogue.common.accessToken.lifeCycle.IAccessTokenService;
import cn.mrcode.wxsdk.core.dialogue.common.exception.ReqException;
import cn.mrcode.wxsdk.core.dialogue.common.exception.WxException;
import cn.mrcode.wxsdk.core.dialogue.protocol.base.AccessTokenInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Date;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author zhuqiang
 * @version V1.0
 * @Description: AccessToken 生命周期维护,单机版
 * @date 2015/9/22 15:25
 */
public class AccessTokenSingleService implements IAccessTokenService {
    private static Logger log = LoggerFactory.getLogger(AccessTokenSingleService.class);

    // 供 获取字符串 token 方法调用
    private static final ConcurrentHashMap<String, AccessTokenInfo> accessTokenMap = new ConcurrentHashMap<String, AccessTokenInfo>();
    // 供 task 维护生命周期调用
    private static final DelayQueue<AccessTokenInfo> accessTokenQueue = new DelayQueue<AccessTokenInfo>();
    /**
     * AccessToken 更新策略：
     * 如果是多用户：那么只使用一个线程去更新是没有办法控制时间的，因为你不知道有哪些appid。除非你事先知道。
     * 所以这个策略和场景是很难遇到的（如果你做的是平台，那么有新加入进来的呢？）
     * 1：首先在getAccesstoken(String appid, String secret)中使用，没有获取到 则主动获取一次并加入map中，以供自动维护生命周期的任务 去维护
     * 2：使用单线程定时去维护已经存在的accesstoken
     * 3：因为超时时间是 2个小时。单线程task设置10分钟的短时间去循环获取。能很大程度上保证 getAccesstoken(String appid, String secret)
     *      除了第一次需要主动去获取token外，其他时间获取都是秒取。 提高了单条命中率。和性能
     */
    static {
        AccessTokenSingleTask att = new AccessTokenSingleTask(accessTokenQueue,accessTokenMap);
//        att.openTimeOutDestroy(true, ConfigContext.accesstokenTimeOut * 2);  //2次超时还未被使用，则清除该token
        new Thread(att).start();
    }
    private static Lock lock = new ReentrantLock();

    @Override
    public String getAccesstoken(String appid, String secret) throws ReqException, WxException {
        lock.lock();
        try {
            boolean flag = accessTokenMap.containsKey(appid);
            if (flag) {
                AccessTokenInfo accessTokenInfo = accessTokenMap.get(appid);
                accessTokenInfo.setLastUseTime(new Date());
                return accessTokenInfo.getAccessToken();
            } else {
                AccessTokenInfo accessTokenInfo = AccessTokenApi.getAccessTokenInfo(appid, secret);
                accessTokenMap.put(appid, accessTokenInfo);
                accessTokenQueue.add(accessTokenInfo);
                accessTokenInfo.setLastUseTime(new Date());
                return accessTokenInfo.getAccessToken();
            }
        } finally {
            lock.unlock();
        }
    }

    private AccessTokenSingleService(){};
    private static  AccessTokenSingleService instance = new AccessTokenSingleService();
    public static AccessTokenSingleService getInstance(){
        return instance;
    }
}
